/**
 * Created by wm123 on 2017/5/23.
 */
class Queue {
    constructor() {
        this.queue = [];
        this.timer = false;
    }
    exec() {
        if (this.queue.length) {
            clearTimeout(this.timer);
            this.timer = process.nextTick(() => {
                this.queue.shift().call();
                this.exec();
            });
        }
    }
    execute(executor) {
        this.queue.push(executor);
        this.exec();
    }
}

class Listener {
    constructor(dispatcher) {
        this.dispatcher = dispatcher;
        this.listeners = [];
        this.callbacks = [];
        this.queue = new Queue();
    }
    add(listener, id) {
        let index = this.listeners.findIndex(fun => fun === listener || typeof fun.id !== 'undefined' && fun.id === id);
        if (index >= 0) this.listeners.splice(index, 1);
        listener.id = id;
        this.listeners.push(listener);
    }
    remove(listener) {
        let index = this.listeners.findIndex(fun => fun === listener || typeof fun.id !== 'undefined' && fun.id === listener);
        if (index >= 0) this.listeners.splice(index, 1);
    }
    removeAll() {
        this.listeners = [];
    }
    dispatch(callback) {
        this.callbacks.push(callback);
        if (this.callbacks.length === 1) {
            this.queue.execute(() => {
                this.dispatcher.dispatching = true;
                this.listeners.forEach(cb => cb && cb.call(this.dispatcher, this.dispatcher));
                this.callbacks.forEach(cb => cb.call());
                this.callbacks = [];
                this.dispatcher.dispatching = false;
            });
        }
    }
    get size() {
        return this.listeners.length;
    }
}
class ListenerMap {
    constructor() {
        this.listeners = new WeakMap();
    }
    add(dispatcher, listener, id) {
        this.get(dispatcher).add(listener, id);
    }
    remove(dispatcher, listener) {
        this.get(dispatcher).remove(listener);
    }
    removeAll(dispatcher) {
        this.get(dispatcher).removeAll();
    }
    get(dispatcher) {
        if (this.listeners.has(dispatcher)) {
            return this.listeners.get(dispatcher);
        } else {
            let listener = new Listener(dispatcher);
            this.listeners.set(dispatcher, listener);
            return listener;
        }
    }
    dispatch(dispatcher, callback) {
        this.get(dispatcher).dispatch(callback);
    }
    size(dispatcher) {
        return this.get(dispatcher).size;
    }
}
class Dispatcher {
    constructor() {
        this.listeners = new ListenerMap();
        this.dispatching = false;
        let dispatcher = new Proxy(this, {
            set(target, property, value, receiver) {
                Reflect.set(target, property, value, receiver);
                if (!property.startsWith('~') && property !== 'dispatching') {
                    if (!dispatcher.dispatching) dispatcher.dispatch();
                }
                return true;
            },
            get(target, property, receiver) {
                let value = Reflect.get(target, property, receiver);
                if (!['on', 'off', 'clear', 'dispatch', 'constructor', 'dispatching'].includes(property) && !(property in { }) && (value instanceof Function || typeof value === 'function')) {
                    return function() {
                        let result = value.apply(this, arguments);
                        if (!dispatcher.dispatching) dispatcher.dispatch();
                        return result;
                    };
                } else {
                    return value;
                }
            },
            deleteProperty(target, property) {
                if (Reflect.get(target, property, dispatcher) instanceof Dispatcher) Reflect.get(target, property, dispatcher).off(dispatcher);
                Reflect.deleteProperty(target, property);
                if (!property.startsWith('~')) {
                    Reflect.set(target, '~' + property, true);
                    if (!dispatcher.dispatching) dispatcher.dispatch();
                }
                return true;
            }
        });
        //dispatcher.dispatch();
        return dispatcher;
    }
    on(listener, id, append = false) {
        this.listeners.add(this, listener, id);
        if(!append) this.dispatch();
    }
    off(listener) {
        this.listeners.remove(this, listener);
    }
    clear() {
        this.listeners.removeAll(this);
    }
    refresh() {
        //do nothing
    }
    dispatch() {
        this.listeners.dispatch(this, () => {
            for (let name in this) {
                if (this.hasOwnProperty(name)) {
                    if (name.startsWith('~')) {
                        delete this[name];
                    }
                }
            }
        });
    }
    dispose() {
        this.clear();
    }
    get data() {
        let result = { };
        for (let name in this) {
            if (this.hasOwnProperty(name)) {
                if (name !== 'data' && name !== 'listeners') {
                    let value = this[name];
                    if (value instanceof Dispatcher) {
                        result[name] = value.data;
                    } else {
                        result[name] = value;
                    }
                }
            }
        }
        return result;
    }
}

module.exports = Dispatcher;